package com.jnc.pay.core.config.redis.lock;


import com.jnc.pay.constant.RedisConstant;
import com.jnc.pay.core.exception.LockException;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @Auther: jjn
 * @Date: 2020/7/22
 * @Desc:  redisson分布式锁实现，基本锁功能的抽象实现
 * 本接口能满足绝大部分的需求，高级的锁功能，请自行扩展或直接使用原生api
 */
@Component
public class RedissonDistributedLock implements DistributedLock {

    @Autowired
    private RedissonClient redisson;

    /**
     * 获取锁，如果获取不成功则一直等待，直到lock被获取
     * @param key       锁的key
     * @param leaseTime 加锁的时间，超过这个时间后锁便自动解锁；如果leaseTime为-1，则保持锁定，直到显式解锁
     * @param unit      {@code leaseTime} 参数的时间单位
     * @param isFair    是否公平锁
     * @return
     * @throws Exception
     */
    @Override
    public Object lock(String key, long leaseTime, TimeUnit unit, boolean isFair) throws Exception {
        RLock lock = getLock(key, isFair);
        lock.lock(leaseTime, unit);
        return lock;
    }
    @Override
    public Object lock(String key, long leaseTime, TimeUnit unit) throws Exception {
        return lock(key, leaseTime, unit, false);
    }
    @Override
    public Object lock(String key, boolean isFair) throws Exception {
        return lock(key, -1, null, isFair);
    }
    @Override
    public Object lock(String key) throws Exception {
        return lock(key, -1, null, false);
    }


    /**
     * 尝试获取锁，如果锁不可用则等待最多waitTime时间后放弃
     * @param key        锁的key
     * @param waitTime   获取锁的最大尝试时间(单位 {@code unit})
     * @param leaseTime  加锁的时间，超过这个时间后锁便自动解锁；如果leaseTime为-1，则保持锁定，直到显式解锁
     * @param unit       {@code waitTime} 和 {@code leaseTime} 参数的时间单位
     * @param isFair     是否公平锁
     * @return
     * @throws Exception
     */
    @Override
    public Object tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, boolean isFair) throws Exception {
        RLock lock = getLock(key, isFair);
        if(lock.tryLock(waitTime, leaseTime, unit)){
            return lock;
        }
        return null;
    }
    @Override
    public Object tryLock(String key, long waitTime, long leaseTime, TimeUnit unit) throws Exception {
        return tryLock(key, waitTime, leaseTime, unit, false);
    }
    @Override
    public Object tryLock(String key, long waitTime, TimeUnit unit, boolean isFair) throws Exception {
        return tryLock(key, waitTime, -1, unit, isFair);
    }

    @Override
    public Object tryLock(String key, long waitTime, TimeUnit unit) throws Exception {
        return tryLock(key, waitTime, -1, unit, false);
    }

    @Override
    public void unlock(Object lock) throws Exception {
        if(null != lock){
            if(lock instanceof RLock){
                RLock rLock = (RLock) lock;
                if(rLock.isLocked()){
                    rLock.unlock();
                }
            }else{
                throw new LockException("requires RLock type");
            }
        }
    }

    private RLock getLock(String key, boolean isFair){
        if(isFair){
            return redisson.getFairLock(RedisConstant.LOCK_KEY_PREFIX + key);
        }
        return redisson.getLock(RedisConstant.LOCK_KEY_PREFIX + key);
    }
}
